#![allow(dead_code)]

use gl;
use crate::shaders::{new_shader_module, c_str};
use core::ptr;
use std::ffi::{CStr};
use super::gl_uniform::AsGLUniform;
use super::gl_uniform::GLUniform::*;

pub struct ShaderProgram {
    id: u32
}

impl ShaderProgram {
    pub fn from_source(vert: &[u8], frag: &[u8]) -> Self {
        let id = unsafe {
            let handle = gl::CreateProgram();

            let vert = new_shader_module(gl::VERTEX_SHADER, vert).unwrap();
            let frag = new_shader_module(gl::FRAGMENT_SHADER, frag).unwrap();

            gl::AttachShader(handle, vert);
            gl::AttachShader(handle, frag);
            gl::LinkProgram(handle);

            let mut success = 0;
            let mut buffer = [0i8; 512];
            gl::GetShaderiv(handle, gl::LINK_STATUS, &mut success);
            if success == 0 {
                gl::GetShaderInfoLog(handle, 512, ptr::null_mut(), buffer.as_mut_ptr());
                let str = CStr::from_ptr(buffer.as_ptr());
                println!("{}", str.to_str().unwrap());
            }

            gl::DeleteShader(vert);
            gl::DeleteShader(frag);
            handle
        };

        Self {
            id
        }
    }

    pub fn use_program(&self) {
        unsafe { gl::UseProgram(self.id) }
    }

    pub fn set_uniform<U>(&self, name: &str, t: U) where U: AsGLUniform {
        let location = unsafe { gl::GetUniformLocation(self.id, c_str(name)) };
        unsafe {
            match t.as_gl_uniform() {
                Int(n) => gl::ProgramUniform1i(self.id, location, n),
                Float(n) => gl::ProgramUniform1f(self.id, location, n),
                Bool(b) => gl::ProgramUniform1i(self.id, location, b as i32),
                Mat4(a) => gl::ProgramUniformMatrix4fv(self.id, location, 1, gl::FALSE, a.as_ptr()),
                Vec4(a) => gl::ProgramUniform4fv(self.id, location, 1, a.as_ptr()),
                Vec3(a) => gl::ProgramUniform3fv(self.id, location, 1, a.as_ptr()),
            }
        }
    }
}